package ldh.serialize;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import ldh.serialize.enumeration.NoSerialize;
import ldh.serialize.enumeration.Serialize;
import ldh.serialize.register.Register;
import ldh.serialize.type.*;
import ldh.serialize.util.SerializedUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SerializedConfig {

	private static final Logger logger = LoggerFactory.getLogger(SerializedConfig.class);
	
	private static ConcurrentMap<Class<?>, List<SerializedItem>> serializedMap = new ConcurrentHashMap<Class<?>, List<SerializedItem>>();

	static {
		Class[] classes = new Class[]{String.class, Integer.class, int.class, Short.class, short.class, Float.class,
				float.class, Double.class, double.class, Byte.class, byte.class, Character.class, char.class,
				Boolean.class, boolean.class, Date.class, java.sql.Date.class};
		for (Class clazz : classes) {
			serializedMap.put(clazz, Arrays.asList(new SerializedItem(clazz)));
		}
	}

	public static void config(Class<?> clazz) {
		if (serializedMap.containsKey(clazz)) return;
		Set<Method> methodSet = SerializedUtil.getAllMethods(clazz);
		//必须满足: set and get
		List<Method[]> serializedMethods = filterSerializedMethod(methodSet);
		List<SerializedItem> items = new ArrayList<SerializedItem>();
		for (Method[] mm : serializedMethods) {
			if (ifNoSerialize(mm)) continue;
			Class<?> returnType = mm[1].getReturnType();
			Serialized<?> s = annotationSerialized(mm);
			if (s == null) s = Register.getInstance().getSerialized(returnType);
			if (s == null) s = otherType(clazz, mm, returnType);
			if (s == null && serializedMap.containsKey(returnType)) {
				s = new WapSerialized(returnType);
			}
			if (s != null) {
				SerializedItem<?, ?> si = new SerializedItem(mm[1], mm[0], s);
				items.add(si);
			}
		}
		serializedMap.putIfAbsent(clazz, items);
	}
	
	private static boolean ifNoSerialize(Method[] mm) {
		NoSerialize sz = mm[0].getAnnotation(NoSerialize.class);
		if (sz != null) {
			return true;
		}
		sz = mm[1].getAnnotation(NoSerialize.class);
		if (sz != null) {
			return true;
		}
		return false;
	}

	public static List<SerializedItem> getSerializeItems(Class<?> clazz) {
		config(clazz);
		if (serializedMap.containsKey(clazz)) {
			return serializedMap.get(clazz);
		}
		throw new RuntimeException("pleans config " + clazz);
	}

	public static List<SerializedItem> getSerializeItems(Method method) {
		List<SerializedItem> items = new ArrayList<SerializedItem>();
		Class<?>[] classes = method.getParameterTypes();
		for (Class clazz : classes) {
			List<SerializedItem> itemList = getSerializeItems(clazz);
		}
		throw new RuntimeException("pleans config " + method);
	}

	private static List<Method[]> filterSerializedMethod(Set<Method> methodSet) {
		List<Method[]> methods = new ArrayList<Method[]>();
		for (Method m : methodSet) {
			if (isStartSet(m) && isOneParam(m)) {
				Method getMethod = getGetMethod(m, methodSet);
				if (getMethod != null) {
					methods.add(new Method[]{m, getMethod});
				}
			}
		}
		return methods;
	}

	private static Method getGetMethod(Method m, Set<Method> methodSet) {
		String methodName = "get" + m.getName().substring(3);
		for (Method mm : methodSet) {
			if (mm.getName().equals(methodName)) {
				return mm;
			}
		}
		return null;
	}

	private static boolean isStartSet(Method m) {
		String methodName = m.getName();
		if (methodName.startsWith("set")) {
			return true;
		}
		return false;
	}
	
	private static boolean isOneParam(Method m) {
		if (m.getGenericParameterTypes().length == 1) {
			return true;
		}
		return false;
	}
	
	private static Serialized<?> annotationSerialized(Method[] mm) {
		Serialize sz = mm[0].getAnnotation(Serialize.class);
		Serialized<?> s = null;
		if (sz == null) {
			sz = mm[1].getAnnotation(Serialize.class);
		} 
		if (sz != null) {
			Class<?> pc = sz.clazz();
			if (pc != null) s = Register.getInstance().getSerialized(pc);
			String name = sz.name();
			if (!name.equals("") && s != null) s = Register.getInstance().getSerialized(name);
		}
		return s;
	}
	
	private static Serialized<?> otherType(Class<?> clazz, Method[] mm, Class<?> returnType) {
		if (returnType.isAssignableFrom(SerializedValue.class)) {
			Type t = returnType.getGenericSuperclass(); 
			Serialized<?> paramSerialized = Register.getInstance().getSerialized((Class)t);
			if (paramSerialized != null) {
				return new PackSerialized(paramSerialized, returnType);
			}
		} else if (returnType.isAssignableFrom(List.class) || returnType.isAssignableFrom(Set.class)) {
			Type[] types=mm[0].getGenericParameterTypes();  
		    ParameterizedType pType=(ParameterizedType)types[0];  
		    Type[] tt = pType.getActualTypeArguments();
			Serialized<?> paramSerialized = Register.getInstance().getSerialized((Class)tt[0]);
			if (paramSerialized != null) {
				return new IterableSerialized(paramSerialized);
			}
		} else if (returnType.isAssignableFrom(Map.class)) {
			Type[] types=mm[0].getGenericParameterTypes();  
		    ParameterizedType pType=(ParameterizedType)types[0];  
		    Type[] tt = pType.getActualTypeArguments();
		    Serialized<?> p1 = Register.getInstance().getSerialized((Class)tt[0]);
		    Serialized<?> p2 = Register.getInstance().getSerialized((Class)tt[1]);
		    if (p1 != null && p2 != null) {
		    	return new MapSerialized(p1, p2);
		    }
		} else if (returnType.isEnum()) {
			return new EnumSerialized(returnType);
		} else if (clazz != returnType) {
			config(returnType);
			return null;
		} else if (clazz == returnType) {
			return new TreeSerialized(returnType);
		}
		throw new RuntimeException("no the type " + returnType);
	}
}
